package com.ddwl.user.service;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.ddwl.common.constant.*;
import com.ddwl.common.exception.*;
import com.ddwl.common.feign.api.ShopApi;
import com.ddwl.common.feign.wx.WxAuthApi;
import com.ddwl.common.feign.wx.WxUserApi;
import com.ddwl.common.service.AuthService;
import com.ddwl.common.service.RedisService;
import com.ddwl.common.util.BeanConverUtils;
import com.ddwl.common.util.EncryptUtil;
import com.ddwl.common.util.ObjectUtil;
import com.ddwl.common.util.UUIDGenerator;
import com.ddwl.schema.bo.shop.ShopUserRecordBo;
import com.ddwl.schema.bo.user.AppLoginBo;
import com.ddwl.schema.bo.user.AuthBo;
import com.ddwl.schema.bo.user.LoginBo;
import com.ddwl.schema.bo.user.PwdBo;
import com.ddwl.schema.vo.gate.wechat.WechatUserInfoVo;
import com.ddwl.schema.vo.gate.wechat.WxTokenVo;
import com.ddwl.schema.vo.shop.ShopAccountVo;
import com.ddwl.schema.vo.shop.ShopVo;
import com.ddwl.schema.vo.user.LoginVo;
import com.ddwl.schema.vo.user.OAuthVo;
import com.ddwl.user.constant.LoginTypeEnum;
import com.ddwl.user.dao.AccountDao;
import com.ddwl.user.dao.UserAuthDao;
import com.ddwl.user.dao.UserDao;
import com.ddwl.user.dao.model.Account;
import com.ddwl.user.dao.model.User;
import com.ddwl.user.dao.model.UserAuth;
import io.gitee.tooleek.lock.spring.boot.annotation.Lock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author Fu Zhaohui
 * @version 1.0.0
 * @date 2019/8/19 17:01
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LoginService {

    private final RedisService redisService;
    private final UserDao userDao;
    private final UserAuthDao userAuthDao;
    private final AccountDao accountDao;
    private final TokenService tokenService;
    private final AuthService authService;
    private final UserService userService;
    private final StringRedisTemplate template;
    private final ShopApi shopApi;
    private final RabbitTemplate rabbitTemplate;
    private final WxAuthApi wxAuthApi;
    private final WxUserApi wxUserApi;

    public LoginVo appLogin(AppLoginBo bo) {
        User user = null;
        switch (LoginTypeEnum.getByValue(bo.getType())) {
            case SMS:
                String key = userService.verifySMSCaptcha(bo.getMobile(), bo.getCode(), SmsCaptchaNodeEnum.LOGIN);
                template.delete(key);
                user = userDao.queryByMobile(bo.getMobile());
                if (user == null) {
                    throw new CommonException(ExceptionCode.LOGIN_FAIL);
                }
                break;
            case WX:
                ParamException.isNull(bo.getAppId(), "appId不能为空");
                ParamException.isNull(bo.getOpenId(), "openId不能为空");
                UserAuth userAuth = userAuthDao.getByOpenId(bo.getOpenId());
                ParamException.isNull(userAuth, "当前微信未注册");
                ParamException.isFalse(bo.getAppId().equals(userAuth.getAppId()), "appId不正确");
                user = userDao.selectByPrimaryKey(userAuth.getUserId());
                if (user == null) {
                    throw new CommonException(ExceptionCode.LOGIN_FAIL);
                }
                break;
            case PASSWORD:
                user = userDao.queryByMobile(bo.getMobile());
                if (user == null) {
                    throw new CommonException(ExceptionCode.LOGIN_FAIL);
                }
                if (!EncryptUtil.encryptMD5(user.getSalt() + bo.getPassword()).equals(user.getPassword())) {
                    throw new CommonException(ExceptionCode.LOGIN_FAIL);
                }
        }
        LoginVo vo = BeanConverUtils.convertBean(user, LoginVo.class);
        LoginBo loginBo = BeanConverUtils.convertBean(bo, LoginBo.class);
        loginBo.setId(user.getId());
        loginBo.setNickname(user.getNickname());
        vo.setUserName(user.getMobile());
        vo.setToken(tokenService.getToken(loginBo));
        ((LoginService) AopContext.currentProxy()).updateUserClientId(user.getId(), bo.getClientId());
        return vo;
    }

    public LoginVo login(LoginBo bo) {
        LoginVo vo = new LoginVo();
        String product = bo.getProduct();
        switch (BaseEnum.ProductEnum.getProduct(product)) {
            case CMS:
            case CONSOLE:
                String code = redisService.getValueOps().get(RedisKey.IMG_CODE_KEY.getKey(bo.getUuid()));
                if (!StringUtils.hasText(bo.getCode()) || !bo.getCode().equalsIgnoreCase(code)) {
                    throw new CommonException(ExceptionCode.IMG_CODE_ERROR);
                }
                Account account = accountDao.queryAccountByUserName(bo.getUsername());
                if (account == null) {
                    throw new CommonException(ExceptionCode.LOGIN_FAIL);
                }
                if(account.getStatus().equals(BaseEnum.StatusEnum.DISABLE.getCode())) {
                    throw new CommonException(ExceptionCode.ACCOUNT_DISABLE);
                }
                if (!EncryptUtil.encryptMD5(account.getSalt() + bo.getPassword()).equals(account.getPassword())) {
                    throw new CommonException(ExceptionCode.LOGIN_FAIL);
                }
                BeanUtils.copyProperties(account, bo, ObjectUtil.getNullFields(account));
                vo.setToken(tokenService.getToken(bo));
                vo.setId(account.getId());
                vo.setNickname(account.getNickname());
                vo.setUserName(account.getUsername());
                break;
            case SHOP_CONSOLE:
                ParamException.isTrue((null == bo || StrUtil.isEmpty(bo.getMobile())), "手机号不能为空");
                ShopAccountVo shopVo = shopApi.getUserShopByMobile(bo.getMobile()).getResult();
                if (shopVo == null) {
                    throw new CommonException(ExceptionCode.LOGIN_FAIL);
                }
                if (LoginTypeEnum.SMS.getValue().equals(bo.getType())) {
                    String key = userService.verifySMSCaptcha(bo.getMobile(), bo.getCode(), SmsCaptchaNodeEnum.LOGIN);
                    template.delete(key);
                } else {
                    code = redisService.getValueOps().get(RedisKey.IMG_CODE_KEY.getKey(bo.getUuid()));
                    if (!StringUtils.hasText(bo.getCode()) || !bo.getCode().equalsIgnoreCase(code)) {
                        throw new CommonException(ExceptionCode.IMG_CODE_ERROR);
                    }
                    if (!EncryptUtil.encryptMD5(shopVo.getSalt() + bo.getPassword()).equals(shopVo.getPassword())) {
                        throw new CommonException(ExceptionCode.LOGIN_FAIL);
                    }
                }
                BeanUtils.copyProperties(shopVo, bo);
                bo.setId(shopVo.getUserId());
                vo.setToken(tokenService.getToken(bo));
                vo.setId(shopVo.getUserId());
                vo.setNickname(shopVo.getNickname());
                vo.setUserName(shopVo.getUsername());
                break;
            default:
                throw new IllegalException();
        }
        return vo;
    }

    @Async
    public void updateUserClientId(String userId, String clientId) {
        if (StrUtil.isNotEmpty(userId) && StrUtil.isNotEmpty(clientId) && !clientId.equals("null")) {
            User user = new User();
            user.setId(userId);
            user.setClientId(clientId);
            userDao.updateByPrimaryKeySelective(user);
        }
    }

    public void logout(String product, String userId) {
        authService.clearAuth(product, userId);
    }

    @Lock(leaseTime = 5)
    @Transactional(rollbackFor = Throwable.class)
    public LoginVo auth(AuthBo bo) {
        User user = null;
        // 小程序有获取sessionKey
        if (StringUtils.hasText(bo.getSessionKey())) {
            Map<String, String> sessionKeyMap = redisService.getHashOps().entries(RedisKey.WX_SESSION_KEY.getKey(bo.getSessionKey()));
            if (CollectionUtils.isEmpty(sessionKeyMap)) {
                throw new AuthException("认证授权信息失败");
            }
            String unionId = sessionKeyMap.get("unionId");
            user = userDao.getByUnionId(unionId);
        } else {
            // App已自己获取了openId与unionId
            if (StringUtils.hasText(bo.getUnionId())) {
                user = userDao.getByUnionId(bo.getUnionId());
                if (user == null) {
                    user = new User();
                    BeanUtils.copyProperties(bo, user);
                    user.setId(UUIDGenerator.getShortUUID());
                    user.setType(UserEnum.UserTypeEnum.MEMBER.getValue());
                    if (StrUtil.isNotBlank(bo.getInviteId())) {
                        User sup = userDao.selectByPrimaryKey(bo.getInviteId());
                        if (sup != null) {
                            user.setIndirectSuperior(sup.getImmediateSuperior());
                            user.setIndirectSuperiorName(sup.getImmediateSuperiorName());
                            user.setImmediateSuperior(sup.getId());
                            user.setImmediateSuperiorName(sup.getNickname());
                            user.setTopAgent(sup.getTopAgent());
                        } else {
                            log.error("小程序注册未找到邀请人authBo={}", JSON.toJSONString(bo));
                        }
                    }
                    userDao.insert(user);
                    userService.addPopularizeNum(user);
                }
            }

            UserAuth auth = userAuthDao.getByOpenId(bo.getOpenId());
            if (auth == null) {
                // FIXME 特殊情况 App未加入到开放平台 没有传unionId情况
                if (user == null) {
                    user = new User();
                    BeanUtils.copyProperties(bo, user);
                    user.setId(UUIDGenerator.getShortUUID());
                    user.setType(UserEnum.UserTypeEnum.MEMBER.getValue());
                    if (StrUtil.isNotBlank(bo.getInviteId())) {
                        User sup = userDao.selectByPrimaryKey(bo.getInviteId());
                        if (sup != null) {
                            user.setIndirectSuperior(sup.getImmediateSuperior());
                            user.setIndirectSuperiorName(sup.getImmediateSuperiorName());
                            user.setImmediateSuperior(sup.getId());
                            user.setImmediateSuperiorName(sup.getNickname());
                            user.setTopAgent(sup.getTopAgent());
                        } else {
                            log.error("小程序注册未找到邀请人authBo={}", JSON.toJSONString(bo));
                        }
                    }
                    userDao.insert(user);
                    userService.addPopularizeNum(user);
                }
                auth = BeanConverUtils.convertBean(bo, UserAuth.class);
                auth.setOpenId(bo.getOpenId());
                auth.setUserId(user.getId());
                userAuthDao.add(auth);
                redisService.getHashOps().increment(RedisKey.DATA_TODAY_CHART.getKey(), DataEnum.TodayData.REGISTERS.name(), 1);
            } else {
                user = userDao.selectByPrimaryKey(auth.getUserId());
            }
        }
        LoginBo loginBo = new LoginBo();
        BeanUtils.copyProperties(bo, loginBo);
        loginBo.setId(user.getId());
        LoginVo vo = new LoginVo();
        vo.setId(user.getId());
        loginBo.setMobile(user.getMobile());
        loginBo.setChannel(user.getChannel());
        loginBo.setNickname(user.getNickname());
        vo.setToken(tokenService.getToken(loginBo));

        ShopUserRecordBo bo1 = new ShopUserRecordBo();
        bo1.setLastUrl("login");
        bo1.setShopId(bo.getShopId());
        bo1.setLoginTime(Instant.now());
        bo1.setUserId(user.getId());
        bo1.setUserName(user.getNickname());
        bo1.setAvatar(user.getAvatar());
        rabbitTemplate.convertAndSend(RabbitMqConstants.SHOP_USER_RECORD_QUENE, JSON.toJSONString(bo1));
        return vo;
    }

    @Transactional(rollbackFor = Throwable.class)
    public void changePwd(PwdBo bo) {
        Account entity = accountDao.selectByPrimaryKey(bo.getUserId());
        if (entity == null) {
            throw new CommonException(ExceptionCode.DATA_NOT_EXISTS);
        }
        String saltPassword = EncryptUtil.encryptMD5(entity.getSalt() + bo.getOldPwd());
        if (!saltPassword.equals(entity.getPassword())) {
            throw new CommonException("旧密码错误");
        }
        Account update = new Account();
        update.setId(entity.getId());
        update.setPassword(EncryptUtil.encryptMD5(entity.getSalt() + bo.getNewPwd()));
        accountDao.updateByPrimaryKeySelective(update);
    }

    /**
     * 第三方扫码登录信息记录
     *
     * @param appId
     * @param pk
     * @param code
     * @param uuid
     */
    public OAuthVo oauthInfo(String appId, String pk, String code, String uuid) {
        WxTokenVo result = wxAuthApi.oauth2(appId, redisService.getHashOps().get(RedisKey.WX_ACCOUNT_KEY.getKey(appId), "appSecret"), code, "authorization_code");
        log.info("微信认证返回：{}", JSON.toJSONString(result));
        if(!StringUtils.hasText(result.getOpenid())) {
            throw new CommonException("授权已过期，请重新扫码登录");
        }
        UserAuth userAuth = userAuthDao.getByOpenId(result.getOpenid());
        User user;
        if (userAuth == null) {
            WechatUserInfoVo userInfo = wxUserApi.userInfo(redisService.getHashOps().get(RedisKey.WX_TOKEN_KEY.getKey(appId), "accessToken"), result.getOpenid());
            log.info("微信用户信息返回：{}", JSON.toJSONString(userInfo));
            if(!StringUtils.hasText(userInfo.getUnionid())) {
                throw new CommonException("微信获取用户异常");
            }
            user = userDao.getByUnionId(userInfo.getUnionid());
            if (user == null) {
                log.info("无用户授权 -> {} -> {}", JSON.toJSONString(result), JSON.toJSONString(userInfo));
                throw new CommonException(ExceptionCode.USER_NOT_EXISTS);
            }
            userAuth = new UserAuth();
            userAuth.setOpenId(result.getOpenid());
            userAuth.setAppId(appId);
            userAuth.setProduct(pk);
            userAuth.setUserId(user.getId());
            userAuthDao.add(userAuth);
        } else {
            user = userDao.selectByPrimaryKey(userAuth.getUserId());
        }
        ShopVo shop = shopApi.getShopByUserId(userAuth.getUserId()).getResult();
        if (shop == null) {
            log.warn("用户{}查询到开店信息，请核实", userAuth.getUserId());
            throw new CommonException("您还未开店，请到五八马里面进行开店吧");
        }
        Map<String, String> tokenMap = new HashMap<>(2);
        tokenMap.put("uid", user.getId());
        tokenMap.put("avatar", user.getAvatar());
        tokenMap.put("nickname", Optional.ofNullable(user.getNickname()).orElse(""));
        tokenMap.put("username", Optional.ofNullable(user.getMobile()).orElse(""));
        tokenMap.put("channel", Optional.ofNullable(user.getChannel()).orElse(""));
        tokenMap.put("shopId", shop.getId());
        tokenMap.put("shopName", shop.getName());
        tokenMap.put("dk", result.getOpenid());
        tokenMap.put("pk", pk);
        String key = RedisKey.OAUTH_KEY.getKey(uuid);
        redisService.getHashOps().putAll(key, tokenMap);
        redisService.getHashOps().getOperations().expire(key, 5, TimeUnit.MINUTES);
        return new OAuthVo(result.getOpenid(), shop.getId());
    }

    /**
     * 查询第三方授权状态
     *
     * @param uuid
     */
    public LoginVo oauthCheck(String uuid) {
        Map<String, String> tokenMap = redisService.getHashOps().entries(RedisKey.OAUTH_KEY.getKey(uuid));
        if (CollectionUtils.isEmpty(tokenMap)) {
            return null;
        }
        LoginVo vo = new LoginVo();
        vo.setToken(tokenMap.get("tk"));
        vo.setId(tokenMap.get("uid"));
        vo.setNickname(tokenMap.get("nickname"));
        vo.setUserName(tokenMap.get("username"));
        vo.setAvatar(tokenMap.get("avatar"));
        vo.setDk(tokenMap.get("dk"));
        return vo;
    }

    /**
     * 查询第三方授权确认登录
     *
     * @param uuid
     */
    public void oauthSubmit(String uuid, String openId) {
        String key = RedisKey.OAUTH_KEY.getKey(uuid);
        Map<String, String> tokenMap = redisService.getHashOps().entries(key);
        if (CollectionUtils.isEmpty(tokenMap)) {
            throw new CommonException("二维码已失效，请刷新二维码再试");
        }
        if (!tokenMap.get("dk").equals(openId)) {
            throw new CommonException("授权信息不匹配");
        }
        LoginBo loginBo = new LoginBo();
        loginBo.setId(tokenMap.get("uid"));
        loginBo.setDk(openId);
        loginBo.setProduct(tokenMap.get("pk"));
        loginBo.setMobile(tokenMap.get("username"));
        loginBo.setChannel(tokenMap.get("channel"));
        loginBo.setNickname(tokenMap.get("nickname"));
        loginBo.setShopId(tokenMap.get("shopId"));
        loginBo.setShopName(tokenMap.get("shopName"));
        redisService.getHashOps().put(key, "tk", tokenService.getToken(loginBo));
        redisService.getHashOps().getOperations().expire(key, 15, TimeUnit.SECONDS);
    }

}
